
/*! Ruotong Music - Flexible Color Picker (Vanilla JS)
 *  v1.0.0  |  MIT License
 *  Features:
 *   - HSV plane (Sat/Brightness) + Hue slider
 *   - HEX / RGB inputs (bi-directional)
 *   - Preview swatch
 *   - EyeDropper API support (if available)
 *   - Popover that stays open until clicking outside or pressing ESC
 *   - onChange(hex) callback fired on every change (debounced option)
 *  Usage:
 *    const picker = RTColorPicker.create({ onChange: hex => console.log(hex) });
 *    picker.open(anchorElement); // opens near the button
 *    picker.set('#6FE1FF');      // programmatically set color
 */
(function (global) {
  'use strict';

  function clamp(v, min, max){ return Math.min(max, Math.max(min, v)); }
  function round(n, d=0){ const p = Math.pow(10,d); return Math.round(n*p)/p; }

  // --- Conversions
  function rgbToHex(r, g, b){
    const toHex = (n)=> n.toString(16).padStart(2,'0');
    return `#${toHex(clamp(r,0,255))}${toHex(clamp(g,0,255))}${toHex(clamp(b,0,255))}`.toUpperCase();
  }
  function hexToRgb(hex){
    if(!hex) return null;
    hex = hex.trim();
    if(hex[0] === '#') hex = hex.slice(1);
    if(hex.length===3) hex = hex.split('').map(ch=>ch+ch).join('');
    if(!/^[0-9a-fA-F]{6}$/.test(hex)) return null;
    const r = parseInt(hex.slice(0,2),16);
    const g = parseInt(hex.slice(2,4),16);
    const b = parseInt(hex.slice(4,6),16);
    return {r,g,b};
  }
  function hsvToRgb(h, s, v){
    // h [0..360), s [0..100], v [0..100]
    s = clamp(s,0,100)/100; v = clamp(v,0,100)/100;
    const c = v * s;
    const x = c * (1 - Math.abs((h/60)%2 - 1));
    const m = v - c;
    let r1=0,g1=0,b1=0;
    if (h < 60)       { r1 = c; g1 = x; b1 = 0; }
    else if (h < 120) { r1 = x; g1 = c; b1 = 0; }
    else if (h < 180) { r1 = 0; g1 = c; b1 = x; }
    else if (h < 240) { r1 = 0; g1 = x; b1 = c; }
    else if (h < 300) { r1 = x; g1 = 0; b1 = c; }
    else              { r1 = c; g1 = 0; b1 = x; }
    const r = Math.round((r1 + m) * 255);
    const g = Math.round((g1 + m) * 255);
    const b = Math.round((b1 + m) * 255);
    return {r,g,b};
  }
  function rgbToHsv(r, g, b){
    r/=255; g/=255; b/=255;
    const max = Math.max(r,g,b), min = Math.min(r,g,b);
    const d = max - min;
    let h = 0;
    if (d !== 0){
      switch(max){
        case r: h = 60 * (((g-b)/d) % 6); break;
        case g: h = 60 * (((b-r)/d) + 2); break;
        case b: h = 60 * (((r-g)/d) + 4); break;
      }
    }
    if (h < 0) h += 360;
    const s = max === 0 ? 0 : d / max;
    const v = max;
    return { h: round(h,2), s: round(s*100,2), v: round(v*100,2) };
  }

  function makeEl(tag, cls, html){
    const el = document.createElement(tag);
    if (cls) el.className = cls;
    if (html != null) el.innerHTML = html;
    return el;
  }

  function style(el, obj){ for(const k in obj) el.style[k] = obj[k]; }

  function createUI(){
    const root = makeEl('div','rt-cp-popover', '');
    root.setAttribute('role','dialog');
    root.setAttribute('aria-label','颜色选择器');
    root.tabIndex = -1;

    const header = makeEl('div','rt-cp-actions','');
    const swatch = makeEl('div','rt-cp-swatch','');
    const hexInput = makeEl('input','rt-cp-input rt-cp-hex');
    hexInput.placeholder = '#RRGGBB';
    hexInput.maxLength = 7;

    const eyedropBtn = makeEl('button','rt-cp-btn','吸管取色');
    eyedropBtn.title = '从屏幕吸取颜色（Chrome/Edge 支持）';
    const closeBtn = makeEl('button','rt-cp-btn','关闭');
    closeBtn.title = '按 Esc 也可以关闭';

    header.appendChild(swatch);
    header.appendChild(hexInput);
    header.appendChild(eyedropBtn);
    header.appendChild(closeBtn);

    const plane = makeEl('div','rt-cp-plane','');
    const planeBg = makeEl('div','rt-cp-plane-bg','');
    const thumb = makeEl('div','rt-cp-thumb','');
    plane.appendChild(planeBg);
    plane.appendChild(thumb);

    const hueRow = makeEl('div','rt-cp-row','');
    const hue = makeEl('input','rt-cp-hue');
    hue.type = 'range'; hue.min = 0; hue.max = 360; hue.value = 0;
    hueRow.appendChild(hue);

    const rgbRow = makeEl('div','rt-cp-row','');
    const rInput = makeEl('input','rt-cp-input'); rInput.type='number'; rInput.min=0; rInput.max=255; rInput.placeholder='R';
    const gInput = makeEl('input','rt-cp-input'); gInput.type='number'; gInput.min=0; gInput.max=255; gInput.placeholder='G';
    const bInput = makeEl('input','rt-cp-input'); bInput.type='number'; bInput.min=0; bInput.max=255; bInput.placeholder='B';
    rgbRow.appendChild(rInput); rgbRow.appendChild(gInput); rgbRow.appendChild(bInput);

    root.appendChild(header);
    root.appendChild(plane);
    root.appendChild(hueRow);
    root.appendChild(rgbRow);

    return { root, swatch, hexInput, eyedropBtn, closeBtn, plane, planeBg, thumb, hue, rInput, gInput, bInput };
  }

  function positionPopover(popover, anchor){
    const rect = anchor.getBoundingClientRect();
    const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    const preferLeft = rect.left;
    const preferTop = rect.bottom + 8;
    const width = popover.offsetWidth || 360;
    const height = popover.offsetHeight || 270;
    let left = Math.min(preferLeft, vw - width - 12);
    if (left < 12) left = 12;
    let top = Math.min(preferTop, vh - height - 12);
    if (top < 12) top = Math.max(12, rect.top - height - 8);
    style(popover, { position:'fixed', left: left+'px', top: top+'px' });
  }

  function setHueBackground(input){
    const grad = 'linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)';
    const trackCss = grad;
    // set on element for Firefox + Edge (we cover via CSS too)
    input.style.background = grad;
  }

  function create(opts){
    const options = Object.assign({
      initial: '#6FE1FF',
      onChange: null,
      // if true, waits a tick between rapid updates (drag) to avoid heavy work by host
      debounce: true
    }, opts || {});

    const ui = createUI();
    setHueBackground(ui.hue);

    let state = { h: 200, s: 60, v: 100 };
    let isOpen = false;
    let anchorEl = null;
    let rafId = 0;

    function hex(){ const {r,g,b} = hsvToRgb(state.h, state.s, state.v); return rgbToHex(r,g,b); }
    function setFromHex(hexStr){
      const rgb = hexToRgb(hexStr);
      if (!rgb) return;
      const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
      state = { h: hsv.h, s: hsv.s, v: hsv.v };
      render();
    }

    function updateThumb(){
      const rect = ui.plane.getBoundingClientRect();
      const x = (state.s/100) * rect.width;
      const y = ((100 - state.v)/100) * rect.height;
      style(ui.thumb, { left: x+'px', top: y+'px' });
    }

    function render(){
      // base color on plane
      ui.plane.style.background = `hsl(${state.h} 100% 50%)`;
      // hue slider value
      ui.hue.value = String(state.h);
      // swatch + inputs
      const h = hex();
      ui.swatch.style.background = h;
      ui.hexInput.value = h;
      const {r,g,b} = hsvToRgb(state.h, state.s, state.v);
      ui.rInput.value = r; ui.gInput.value = g; ui.bInput.value = b;
      updateThumb();
      // notify
      if (options.onChange){
        if (options.debounce){
          if (rafId) cancelAnimationFrame(rafId);
          rafId = requestAnimationFrame(()=> options.onChange(h));
        } else {
          options.onChange(h);
        }
      }
    }

    function setByPlane(clientX, clientY){
      const rect = ui.plane.getBoundingClientRect();
      const x = clamp(clientX - rect.left, 0, rect.width);
      const y = clamp(clientY - rect.top, 0, rect.height);
      state.s = round((x / rect.width) * 100, 2);
      state.v = round(100 - (y / rect.height) * 100, 2);
      render();
    }

    // --- events
    let draggingPlane = false;
    ui.plane.addEventListener('pointerdown', (e)=>{
      draggingPlane = true;
      ui.plane.setPointerCapture(e.pointerId);
      setByPlane(e.clientX, e.clientY);
    });
    ui.plane.addEventListener('pointermove', (e)=>{
      if (!draggingPlane) return;
      setByPlane(e.clientX, e.clientY);
    });
    ui.plane.addEventListener('pointerup', (e)=>{
      draggingPlane = false;
      try{ ui.plane.releasePointerCapture(e.pointerId); }catch(_){}
    });
    ui.hue.addEventListener('input', (e)=>{
      state.h = +e.target.value;
      render();
    });
    ui.hexInput.addEventListener('change', (e)=>{
      const v = e.target.value.trim();
      setFromHex(v);
    });
    function bindRgb(input, key){
      input.addEventListener('change', ()=>{
        const r = clamp(parseInt(ui.rInput.value||'0',10),0,255);
        const g = clamp(parseInt(ui.gInput.value||'0',10),0,255);
        const b = clamp(parseInt(ui.bInput.value||'0',10),0,255);
        const hsv = rgbToHsv(r,g,b);
        state = { h: hsv.h, s: hsv.s, v: hsv.v };
        render();
      });
    }
    bindRgb(ui.rInput,'r'); bindRgb(ui.gInput,'g'); bindRgb(ui.bInput,'b');

    ui.eyedropBtn.addEventListener('click', async ()=>{
      if (!('EyeDropper' in window)){
        alert('当前浏览器不支持 EyeDropper（建议 Chrome/Edge 版本较新）。');
        return;
      }
      try{
        const ed = new window.EyeDropper();
        const res = await ed.open();
        if (res && res.sRGBHex) setFromHex(res.sRGBHex);
      }catch(err){
        // user canceled
      }
    });
    ui.closeBtn.addEventListener('click', ()=> api.close());

    function clickOutside(e){
      if (!isOpen) return;
      if (!ui.root.contains(e.target)){
        api.close();
      }
    }
    function onKey(e){
      if (!isOpen) return;
      if (e.key === 'Escape') api.close();
    }
    document.addEventListener('mousedown', clickOutside);
    document.addEventListener('keydown', onKey);

    // --- API
    const api = {
      open(anchor){
        if (isOpen) return;
        isOpen = true;
        anchorEl = anchor || document.body;
        document.body.appendChild(ui.root);
        // ensure size is measured after attach
        requestAnimationFrame(()=>{
          positionPopover(ui.root, anchorEl);
          ui.root.focus();
        });
      },
      close(){
        if (!isOpen) return;
        isOpen = false;
        if (ui.root.parentNode) ui.root.parentNode.removeChild(ui.root);
      },
      set(hexStr){
        setFromHex(hexStr);
      },
      get(){
        return hex();
      },
      mountToButton(button){
        button.addEventListener('click', ()=> api.open(button));
      },
      _debugUI: ui
    };

    // initialize
    setFromHex(options.initial || '#6FE1FF');
    return api;
  }

  global.RTColorPicker = { create };

})(window);
